package i.g.sbl.tree.model;

import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class BasicTree<T> implements Tree<T, BasicTree<T>> {
    private final T root;
    private final LinkedList<BasicTree<T>> children;

    public BasicTree(T root) {
        this.root = root;
        this.children = new LinkedList<>();
    }

    public BasicTree(T root, List<BasicTree<T>> children) {
        this.root = root;
        this.children = new LinkedList<>(children);
    }

    @Override
    public T getRoot() {
        return root;
    }

    @Override
    public Deque<BasicTree<T>> getChildren() {
        return children;
    }

    @Override
    public BasicTree<T> clone() {
        return map(it -> it);
    }

    @Override
    public Optional<BasicTree<T>> filter(Predicate<BasicTree<T>> predicate) {
        if (!predicate.test(this)) {
            return Optional.empty();
        }
        List<BasicTree<T>> filtered = this.getChildren()
                .stream()
                .map(it -> it.filter(predicate))
                .flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty))
                .collect(Collectors.toList());
        return Optional.of(new BasicTree<>(this.root, filtered));
    }

//    @Override
//    public BasicTree<T> map(Function<BasicTree<T>, BasicTree<T>> function) {
//        BasicTree<T> newNode = function.apply(this);
//        List<BasicTree<T>> newChildren = newNode.getChildren().stream().map(it -> it.map(function)).collect(Collectors.toList());
//        return new BasicTree<>(newNode.root, newChildren);
//    }
//


    @Override
    public <I, O extends Tree<I, O>> O map(Function<BasicTree<T>, O> function) {
        BasicTree<I> newNode = (BasicTree<I>) function.apply(this);
        List<BasicTree<I>> newChildren = this.getChildren().stream().map(it -> ((BasicTree<I>)  it.map(function))).collect(Collectors.toList());

        return (O) new BasicTree<I>(newNode.root, newChildren);
    }

    @Override
    public <I, O extends Tree<I, O>> O mapItem(Function<T, I> function) {
        I apply = function.apply(this.root);
        List<BasicTree<I>> collect = new ArrayList<>();
        for (BasicTree<T> it : this.getChildren()) {
            BasicTree<I> mapItem = it.mapItem(function);
            collect.add(mapItem);
        }
        return (O) new BasicTree<>(apply, collect);

    }

    @Override
    public Optional<BasicTree<T>> findFirst(Predicate<BasicTree<T>> predicate) {
        if (predicate.test(this)) {
            return Optional.of(this);
        }
        return getChildren().stream().map(it -> it.findFirst(predicate)).filter(Optional::isPresent).map(Optional::get).findFirst();
    }

    /**
     * 添加子节点
     */
    public BasicTree<T> addChild(BasicTree<T> child) {
        this.getChildren().add(child);
        return this;
    }

    public boolean fullEquals(BasicTree<T> another) {
        if (this == another) return true;
        if (another == null || getClass() != another.getClass()) return false;
        if (!Objects.equals(this.root, another.root)) {
            return false;
        }
        if (this.getChildren().size() != another.getChildren().size()) {
            return false;
        }
        Iterator<BasicTree<T>> thisIterator = this.children.iterator();
        Iterator<BasicTree<T>> otherIterator = another.children.iterator();
        while (thisIterator.hasNext()) {
            if (!thisIterator.next().equals(otherIterator.next())) {
                return false;
            }
        }
        return true;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        BasicTree<?> basicTree = (BasicTree<?>) o;
        return Objects.equals(root, basicTree.root);
    }

    @Override
    public int hashCode() {
        return Objects.hash(root);
    }

    @Override
    public String toString() {
        return "BasicTree{" +
                "root=" + root +
                '}';
    }
}
